#include "Game.h"
#include <Psapi.h>
#include <sstream>
#include <boost\format.hpp>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
	switch(message) {
	case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		} break;

	case WM_KEYDOWN:
		{
			//Game::InputPtr->HandleInput(WM_KEYDOWN);
		} break;

	case WM_KEYUP:
		{
			//Game::InputPtr->HandleInput(WM_KEYUP);
		} break;
	}

	return DefWindowProc(hWnd, message, wParam, lParam);
}

WNDCLASSEX Game::wndClass;
HWND Game::window;
Graphics* Game::GraphicsPtr;
vector<Character*> Game::CharactersPtrs;
vector<int> Game::freeCharacterSlot;
vector<MapTile*> Game::MapTilePtrs;
vector<MapTile*> Game::DecorativeMapTilePtrs;
Game::GameState Game::gameState;
Input* Game::InputPtr;
int Game::playerCharacterId;
Camera* Game::CameraPtr;
MenuManager* Game::MenuManagerPtr;
Cursor* Game::CursorPtr;

int Game::Start(HINSTANCE &hInstance) {
	SecureZeroMemory(&Game::wndClass, sizeof(WNDCLASSEX));

	Game::wndClass.cbSize = sizeof(WNDCLASSEX);
	Game::wndClass.style = CS_HREDRAW | CS_VREDRAW;
	Game::wndClass.lpfnWndProc = WndProc;
	Game::wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	Game::wndClass.hInstance = hInstance;
	Game::wndClass.lpszClassName = L"Game";

	RegisterClassEx(&Game::wndClass);

	RECT wndRect = windowSize;

	AdjustWindowRect(&wndRect, WS_OVERLAPPEDWINDOW, FALSE);

	int posX = (GetSystemMetrics(SM_CXSCREEN) - (wndRect.right - wndRect.left)) / 2;
	int posY = (GetSystemMetrics(SM_CYSCREEN) - (wndRect.bottom - wndRect.top)) / 2 + 10;

	Game::window = CreateWindowEx(
		NULL,
		L"Game",
		L"JandR",
		WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU,
		posX, posY,
		(wndRect.right - wndRect.left), (wndRect.bottom - wndRect.top),
		NULL, NULL,
		hInstance,
		NULL);

	ShowWindow(Game::window, SW_SHOW);

	int error;

	Game::GraphicsPtr = new Graphics;
	error = Game::GraphicsPtr->Initialize(Game::window);

	if(error != 0) {
		Logger::Log(error, "Failed to initialize Graphics. Program terminated.", __FILE__, __LINE__, LOG_ERROR);
		return error;
	}
	
	Game::InputPtr = new Input;
	error = Game::InputPtr->Initialize(hInstance);

	if(error != 0) {
		Logger::Log(error, "Failed to initialize Input. Program terminated.", __FILE__, __LINE__, LOG_ERROR);
		return error;
	}

	Game::CameraPtr = new Camera;
	error = Game::CameraPtr->Initialize();

	if(error != 0) {
		Logger::Log(error, "Failed to initialize Camera. Program terminated.", __FILE__, __LINE__, LOG_ERROR);
		return error;
	}

	Game::MenuManagerPtr = new MenuManager;
	error = Game::MenuManagerPtr->Initialize();

	if(error != 0) {
		Logger::Log(error, "Failed to initialize MenuManager. Program terminated.", __FILE__, __LINE__, LOG_ERROR);
		return error;
	}

	Game::CursorPtr = new Cursor;
	error = Game::CursorPtr->Initialize();

	if(error != 0) {
		Logger::Log(error, "Failed to initialize Cursor. Program terminated.", __FILE__, __LINE__, LOG_ERROR);
		return error;
	}

	/*
	error = Game::SoundPtr->Initialize();

	if(error != 0) {
		Game::LoggerPtr->Log(error, "Failed to initialize Sound. Program terminated.", __FILE__, __LINE__, LOG_ERROR);
		return error;
	}
	*/

	Logger::Log("Game successfully initialized!", __FILE__, __LINE__);

	Game::gameState = Playing;

	Game::LoadLevel();

	Game::GameLoop();

	return 0;
}

void Game::GameLoop() {
	DWORD nextGameTick = GetTickCount();

	MSG msg;

	while(true) {

		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

			TranslateMessage(&msg);
			DispatchMessage(&msg);

			if(msg.message == WM_QUIT) break;
		}
		else {

			Game::InputPtr->HandleInput();
			
			switch(Game::gameState) {
			case Playing:
				{
					//////////////////////////////////////////////////////////////
					//////////////////// Game is playing /////////////////////////
					//////////////////////////////////////////////////////////////

					while(GetTickCount() > nextGameTick) {
						// Clear unneccessary elements with NULL pointers

						static DWORD lastExecution;
						lastExecution = GetTickCount();

						static unsigned short lastGarbageCheck = 0;

						if(lastGarbageCheck > 100) {
							Game::ClearCharacters();
							lastGarbageCheck = 0;
						}
						else lastGarbageCheck++;
						
						// Update what the player is doing based on the current input
						Game::UpdatePlayerActions();
						
						Game::CameraPtr->HandleInput();

						// loop through all Characters and do stuff with them.
						for(unsigned int i = 0; i < Game::CharactersPtrs.size(); i++) {
							if(Game::CharactersPtrs[i] != NULL) {
								Game::CharactersPtrs[i]->Move();					// Move the Character based on current velocity
								Game::CharactersPtrs[i]->NextAnimationFrame();	// Animates the Character based on current moving direction

								// Check if the Character is still alive.
								// Characters can die from 0 hp if they are not invulnerable.
								// Or they can reach their maximum lifetime.
								if(!Game::CharactersPtrs[i]->IsAlive()) {
									// Delete the Character and save the free slot so it can be reused
									delete Game::CharactersPtrs[i];
									Game::CharactersPtrs[i] = NULL;
									if(i == (Game::CharactersPtrs.size() - 1)) {
										Game::CharactersPtrs.pop_back();
									}
									else Game::freeCharacterSlot.push_back(i);
								}
							}
						}

						Game::CameraPtr->UpdatePosition();

						nextGameTick += skipTicks * Game::InputPtr->pageCount;

						DWORD dTick = GetTickCount() - lastExecution;

						if(dTick > skipTicks) {
							ostringstream message;
							message << "Warning! Execution time took " << dTick << " ticks. Game is trying to catch up!" << endl;
							OutputDebugStringA(message.str().c_str());
						}
					}
				
					// render the game
					Game::GraphicsPtr->RenderFrame();

					// was the pause toggled?

					if(Game::InputPtr->ToggledKeys.escape) {
						Game::MenuManagerPtr->ShowMenu("mainmenu");
						Game::gameState = ShowingMenu;
					}

					//////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////
				} break;

			case ShowingMenu:
				{
					Game::GraphicsPtr->RenderFrame();

					if(!Game::InputPtr->ToggledKeys.escape) {
						Game::MenuManagerPtr->ShowMenu("none");
						Game::gameState = Playing;
					}

					if(Game::InputPtr->KeysPressed.endkey) PostQuitMessage(NULL);

					nextGameTick = GetTickCount();
				} break;
			}

		}
	}

	Game::Shutdown();
}

void Game::UpdatePlayerActions() {
	if(Game::InputPtr->KeysPressed.left) Game::CharactersPtrs[Game::playerCharacterId]->SetVelocityX(-3.5f);
	if(Game::InputPtr->KeysPressed.right) Game::CharactersPtrs[Game::playerCharacterId]->SetVelocityX(3.5f);
	if(Game::InputPtr->KeysPressed.up) Game::CharactersPtrs[Game::playerCharacterId]->SetVelocityY(3.5f);
	if(Game::InputPtr->KeysPressed.down) Game::CharactersPtrs[Game::playerCharacterId]->SetVelocityY(-3.5f);
	if(!Game::InputPtr->KeysPressed.left && !Game::InputPtr->KeysPressed.right) Game::CharactersPtrs[Game::playerCharacterId]->SetVelocityX(0.0f);
	if(!Game::InputPtr->KeysPressed.up && Game::CharactersPtrs[Game::playerCharacterId]->GetVY() > 0.0f) Game::CharactersPtrs[Game::playerCharacterId]->SetVelocityY(0.0f);

	static int lastShot = 0;

	if(Game::InputPtr->KeysPressed.del && lastShot >= 20) {
		int CharacterID = Game::AddCharacter(new Character(3, Game::CharactersPtrs[Game::playerCharacterId]->GetCX(), Game::CharactersPtrs[Game::playerCharacterId]->GetCY(), 0.0f, 0.0f, 1000));
		Game::CharactersPtrs[CharacterID]->SetInvulnerable(true);
		if(Game::CharactersPtrs[Game::playerCharacterId]->GetFacingToDirection() == Direction::Right) {
			Game::CharactersPtrs[CharacterID]->SetVelocityX(6.0f + Game::CharactersPtrs[Game::playerCharacterId]->GetVX());
		}
		if(Game::CharactersPtrs[Game::playerCharacterId]->GetFacingToDirection() == Direction::Left) {
			Game::CharactersPtrs[CharacterID]->SetVelocityX(-6.0f + Game::CharactersPtrs[Game::playerCharacterId]->GetVX());
		}
		lastShot = 0;
	}

	lastShot++;
}

Game::GameState Game::GetGamestate() {
	return Game::gameState;
}

int Game::AddCharacter(Character* Character) {

	if(Game::freeCharacterSlot.size() > 0) {
		int freeSlot = Game::freeCharacterSlot[Game::freeCharacterSlot.size()-1];
		Game::CharactersPtrs[freeSlot] = Character;
		Game::freeCharacterSlot.pop_back();
		Game::CharactersPtrs[freeSlot]->InitializeCharacterId(freeSlot);

		return freeSlot;
	}
	else {
		Game::CharactersPtrs.push_back(Character);
		Game::CharactersPtrs[Game::CharactersPtrs.size() - 1]->InitializeCharacterId(Game::CharactersPtrs.size() - 1);
		return (Game::CharactersPtrs.size() - 1);
	}
}

void Game::ClearCharacters() {
	unsigned int i = Game::CharactersPtrs.size() - 1;

	while(Game::CharactersPtrs[i] == NULL) {
		Game::CharactersPtrs.pop_back();
		for(unsigned int j = 0; j < Game::freeCharacterSlot.size(); j++) {
			if(Game::freeCharacterSlot[j] == i) Game::freeCharacterSlot.erase(Game::freeCharacterSlot.begin() + j);
		}
		i--;
	}

	DWORD processID = NULL;
	GetWindowThreadProcessId(Game::window, &processID);

	HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
	PROCESS_MEMORY_COUNTERS pmc;

	if(GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
		ostringstream message;

		message << "Memory: " << pmc.WorkingSetSize << " Bytes" << endl;
		message << "FPS: " << Game::GraphicsPtr->fps << endl;

		OutputDebugStringA(message.str().c_str());
	}
}

void Game::Shutdown() {

	Logger::Log("Shutting down Game...", __FILE__, __LINE__);

	for(unsigned int i = 0; i < Game::CharactersPtrs.size(); i++) {
		if(Game::CharactersPtrs[i] != NULL) delete Game::CharactersPtrs[i];
	}

	Game::GraphicsPtr->Shutdown();

	delete Game::GraphicsPtr;
	delete Game::InputPtr;
	delete Game::CameraPtr;
	delete Game::MenuManagerPtr;

	return;
};

void Game::LoadLevel(int levelID) {
	sqlite3* db;
	sqlite3_stmt* sqlQuery;
	int errcode;

	// connect to game database
	errcode = sqlite3_open("gamedb.s3db", &db);

	if(errcode) {
		Logger::Log(E_DB_CONNECT_FAILED, "SQL ERROR: Failed to open database", __FILE__, __LINE__, LOG_ERROR);
		MessageBox(NULL, L"Failed to open database", L"ERROR", MB_OK | MB_ICONERROR);
		sqlite3_close_v2(db);
	}


	// load Characters

	ostringstream statement;

	statement << "SELECT id FROM game_characters WHERE level_id = " << levelID << ";"; 

	if(sqlite3_prepare_v2(db, statement.str().c_str(), -1, &sqlQuery, NULL) != SQLITE_OK) Logger::Log(E_DB_QUERY_FAILED, "SQL ERROR: Failed to prepare query", __FILE__, __LINE__, LOG_ERROR);

	if(sqlQuery != NULL) {
		while(sqlite3_step(sqlQuery) == SQLITE_ROW) {
			int id = sqlite3_column_int(sqlQuery, 0);

			int CharacterID = Game::AddCharacter(new Character(id)); // create new Character with the id fetched from the database

			if(Game::CharactersPtrs[CharacterID]->IsPlayerCharacter()) Game::playerCharacterId = CharacterID;
		}
	}

	sqlite3_finalize(sqlQuery);

	sqlite3_close_v2(db);

	// loop through the Characters and look for hooked Characters and set the IDs

	for(unsigned int i = 0; i < Game::CharactersPtrs.size(); i++) {
		
		if(Game::CharactersPtrs[i]->GetHookCharacterDBId() >= 0) {
			for(unsigned int j = 0; j < Game::CharactersPtrs.size(); j++) {
				if(Game::CharactersPtrs[j]->GetDBId() == Game::CharactersPtrs[i]->GetHookCharacterDBId()) {
					Game::CharactersPtrs[i]->SetHookCharacterID(Game::CharactersPtrs[j]->GetUId());
					break;
				}
			}
		}
	}

	Game::LoadMap(levelID);
}

void Game::LoadMap(int levelID) {
	sqlite3* db;
	sqlite3_stmt* sqlQuery;
	int errcode;

	// connect to game database
	errcode = sqlite3_open("gamedb.s3db", &db);

	if(errcode) {
		Logger::Log(E_DB_CONNECT_FAILED, "SQL ERROR: Failed to open database", __FILE__, __LINE__, LOG_ERROR);
		MessageBox(NULL, L"Failed to open database", L"ERROR", MB_OK | MB_ICONERROR);
		sqlite3_close_v2(db);
	}


	// load Characters

	ostringstream statement;

	statement << "SELECT id, decoration FROM map_tiles WHERE level_id = " << levelID << ";"; 

	if(sqlite3_prepare_v2(db, statement.str().c_str(), -1, &sqlQuery, NULL) != SQLITE_OK) Logger::Log(E_DB_QUERY_FAILED, "SQL ERROR: Failed to prepare query", __FILE__, __LINE__, LOG_ERROR);

	if(sqlQuery != NULL) {
		while(sqlite3_step(sqlQuery) == SQLITE_ROW) {
			int id = sqlite3_column_int(sqlQuery, 0);
			if(sqlite3_column_int(sqlQuery, 1) != 0) Game::DecorativeMapTilePtrs.push_back(new MapTile(id));
			else Game::MapTilePtrs.push_back(new MapTile(id));
		}
	}

	sqlite3_finalize(sqlQuery);

	sqlite3_close_v2(db);
}